/*
dictionary.cpp
(c) 2010 Yury Hamenkov
This file is part of qWordCards.

qWordCards is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

qWordCards is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with qWordCards. If not, see <http://www.gnu.org/licenses/>.
*/

#include "dictionary.h"
#include <QTextCodec>
#include <QFileInfo>
#include <QTime>
#include <QRegExp>
#if defined(Q_OS_WIN32)
	#include <winsock2.h> // for htonl() func
#elif defined(Q_OS_LINUX)
	#include <netinet/in.h> // for htonl() func
#endif
#include "preferences.h"
#include "wordindex.h"
#include "word.h"

#ifdef DEBUG
	#include <QDebug>
#endif

using namespace qWordCards;

Dictionary::Dictionary(QString filename, QObject *parent) :
		QObject(parent),
		_fileName(""),
		_wordsCount(0),
		_name(""),
		_description(""),
		_hash(""),
		_loaded(false),
		_used(true),
		_error(false),
		_ACTMenuAction(0),
		_errorText(""),
		_loader(filename+".dict.dz")
{
	Q_ASSERT(filename != "");
	qDebug() << filename;
	qDebug() << filename+".dict.dz";
	qDebug() << filename+".idx";
	Q_ASSERT(QFileInfo(filename+".dict.dz").exists());
	Q_ASSERT(QFileInfo(filename+".idx").exists());
	
	_fileName = filename;
	//_name = QFileInfo(filename).baseName();
	_ACTMenuAction = new QAction(_name, this);
}



// Load dictionary
void Dictionary::Load() {
	Q_ASSERT(_fileName != "");

	if (!QFile(_fileName+".idx").exists()) {
		_loaded = false;
		_error = true;
		_errorText = tr("File doesn't exists");
#ifdef DEBUG
		qDebug() << QString("Dictionary loading stopped. File %1 doesn't exists").arg(_fileName+".dict.dz");
#endif
		return;
	}
	
	if (!QFile(_fileName+".dict.dz").exists()) {
		_loaded = false;
		_error = true;
		_errorText = tr("File doesn't exists");
#ifdef DEBUG
		qDebug() << QString("Dictionary loading stopped. File %1 doesn't exists").arg(_fileName+".dict.dz");
#endif
		return;
	}
	

	QFile indexFile(_fileName+".idx");
	if (!indexFile.open(QIODevice::ReadOnly)) {
#ifdef DEBUG
		qDebug() << QString("Cannot open file '%1'. Dictionary scan aborted").arg(_fileName+".idx");
#endif
		_loaded = false;
		_error = true;
		_errorText = tr("Cannot read file");
		return;
	}

	// Create hash to check if file was changed
	QByteArray fileData = indexFile.readAll();
	QByteArray hash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
	indexFile.reset();

	// If dict file was not changed since last loading, interrupt loading
	if (_hash.size() > 0) {
		if (_hash == hash) {
#ifdef DEBUG
			qDebug() << QString("Dict '%1' was loaded earlier and was not changed. Loading interrutped.").arg(_fileName);
#endif
			indexFile.close();
			return;
		} else {
#ifdef DEBUG
			qDebug() << QString("Dict '%1' was loaded earlier, but was changed. Reloadind started.").arg(_fileName);
#endif
		}
	}
	
	if (!_loader.Load()) {
		qDebug() << "Error loading dict file";
		return;
	}

	// Loading index file
	_hash = hash;
	_words.clear();
	_knownWords.clear();
	_favouriteWords.clear();
	
	_loaded = false;
	_error = false;

	QTextCodec * codec = QTextCodec::codecForName("UTF-8");
	QTextCodec::setCodecForCStrings(codec);
	
	
	//----------------------------------------
	char c;
	char* str = new char[255];
	unsigned short int index = 0;
	unsigned long int offset = 0;
	unsigned long int size = 0;
	qint64 res = 0;
	_wordsCount = 0;
	
	while (!indexFile.atEnd()) {
		res = indexFile.read(&c, sizeof(c));
		if (res == -1) {
#ifdef DEBUG
			qDebug() << QString("Dictionary file: '%1'. Error: Reading stream is empty. Dictionary scan aborted").arg(_fileName);
#endif
			_loaded = false;
			_error = true;
			_errorText = "Error reading index file";
			indexFile.close();
			break;
		}
		
		str[index] = c;
		index++;
		
		if (c == '\0') {
			index = 0;
			
			res = indexFile.read((char*)&offset, sizeof(offset));
			if (res == -1) {
#ifdef DEBUG
				qDebug() << QString("Dictionary file: '%1'. Error: Reading stream is empty. Dictionary scan aborted").arg(_fileName);
#endif
				_loaded = false;
				_error = true;
				_errorText = "Error reading index file";
				indexFile.close();
				break;
			}
			offset = htonl(offset);

			res = indexFile.read((char*)&size, sizeof(size));
			if (res == -1) {
#ifdef DEBUG
				qDebug() << QString("Dictionary file: '%1'. Error: Reading stream is empty. Dictionary scan aborted").arg(_fileName);
#endif
				_loaded = false;
				_error = true;
				_errorText = "Error reading index file";
				indexFile.close();
				break;
			}
			size = htonl(size);
			
			WordIndex ind(str, offset, size);
			
			// TODO: А если слово в очереди?
			QString qstr(str);
			if (Preferences::forbiddenWords.Contains(qstr)) {
				_knownWords.insert(qstr, ind);
			} else if (Preferences::favouriteWords.Contains(qstr)) {
				_favouriteWords.insert(qstr, ind);
			} else {
				_words.insert(qstr, ind);
			}
			_wordsCount++;
			
		}
	}
	
	delete [] str;
	if (_error) {return;}
	_loaded = true;
	_error = false;
	emit sg_Reloaded();
	
/* Old loading code
	while(!file.atEnd()) {
		char c;

		qint64 res = file.read(&c, sizeof(c));
		if (res == -1) {
			qDebug() << "Dictionary file: '" << _fileName << "'. Error: Reading stream is empty. Dictionary scan aborted";
			_loaded = false;
			file.close();
			break;
		}

		if (c == '<') {
			if (b_word) {
				word = temp;
			} else if (b_transcription) {
				transcription = temp;
			} else {
				translation = temp;
			}

			temp.clear();
			b_word = false;
			b_transcription = false;

		} else if (c == '>') {
			if (temp == "k") {
				// save previous word
				word = word.trimmed();
				transcription = transcription.trimmed();
				translation = translation.trimmed();
				
				if (word != "") {
					if (Preferences::knownWords.contains(word)) {
						this->knownWords.insert(word, Word(word, transcription, translation));
					} else if (Preferences::favouriteWords.contains(word)) {
						this->favouriteWords.insert(word, Word(word, transcription, translation));
					} else {
						this->words.insert(word, Word(word, transcription, translation));
					}
					wordsLoaded++;
					word.clear();
					translation.clear();
					transcription.clear();
				}

				b_word = true;
			} else if (temp == "tr") {
				b_transcription = true;
			}
			temp.clear();

		} else {
			temp.append(c);
		}


	}
	*/
	
	indexFile.close();
#ifdef DEBUG
	qDebug() << QString("Dictionary index file: '%1' loaded Succesfully").arg(indexFile.fileName());
#endif
	
	// Loading IFO file
	QFile infoFile(_fileName+".ifo");
	if (!infoFile.exists()) {
#ifdef DEBUG
		qDebug() << QString("IFO file does'n exists: '%1'").arg(infoFile.fileName());
#endif
		_name = QFileInfo(infoFile).baseName();
		_description = "";
		return;
	}
	
	if (!infoFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
#ifdef DEBUG
		qDebug() << QString("Cannot open info file '%1'").arg(infoFile.fileName());
#endif
		_name = QFileInfo(infoFile).baseName();
		_description = "";
		return;
	}
	
	
	char* data = new char[1024]; // buffer array
	
	while (!infoFile.atEnd()) {
		res = infoFile.readLine(data, 1023);
		if (res == -1) {
#ifdef DEBUG
			qDebug() << QString("Dictionary file: '%1'. Error: Reading stream is empty. Dictionary scan aborted").arg(infoFile.fileName());
#endif
			_name = QFileInfo(infoFile).baseName();
			_description = "";
			infoFile.close();
			break;
		}
		
		QString s_data(data);
		int equalsIndex = s_data.indexOf('=', 0);
		if (equalsIndex == -1) {
			continue;
		}
		QString id = s_data.mid(0, equalsIndex);
		if (!(id == "bookname" || id == "description")) {
			continue;
		}
		QString value = s_data.mid(equalsIndex + 1, s_data.length() - equalsIndex - 1);
		if (id == "bookname") {
			_name = value;
		} else if (id == "description") {
			_description = value;
		}
		
		
	}
	
	infoFile.close();
	delete [] data;
}


// Returns is dictionary loaded
bool Dictionary::IsLoaded() const {
	return _loaded;
}

QString Dictionary::GetFileName() const {
	return _fileName;
}

bool Dictionary::IsUsed() const {
	return _used;
}

void Dictionary::SetUsed(bool u) {
	_used = u; //TODO: if _loaded == false, _used MUST BE false always
}

QByteArray Dictionary::GetHash() const {
	return _hash;
}

QString Dictionary::GetName() const {
	return _name;
}

unsigned long int Dictionary::GetWordsCount() const {
	return _wordsCount;
}

QString Dictionary::GetDescription() const {
	return _description;
}

const QAction* const Dictionary::Action() const {
	return _ACTMenuAction;
}




void Dictionary::SetFavourite(QString s, bool t) {
	if (t) {
		if (_words.contains(s)) {
			_favouriteWords.insert(s, _words.take(s));
		}
	} else {
		if (!_favouriteWords.contains(s)) {return;}
		if (Preferences::forbiddenWords.Contains(s)) {
			_knownWords.insert(s, _suspendedWords.take(s));
		} else {
			_words.insert(s, _suspendedWords.take(s));
		}
	}
}

void Dictionary::SetKnown(QString s, bool t) {
	if (t) {
		if (_knownWords.contains(s)) {return;}
		if (_favouriteWords.contains(s)) {
			_knownWords.insert(s, _favouriteWords.take(s));
		} else if (_words.contains(s)) {
			_knownWords.insert(s, _words.take(s));
		}
	} else {
		if (!_knownWords.contains(s)) {return;}
		if (Preferences::favouriteWords.Contains(s)) {
			_favouriteWords.insert(s, _knownWords.take(s));
		} else {
			_words.insert(s, _knownWords.take(s));
		}
	}
}

void Dictionary::SetSuspended(QString s, bool t) {
	if (t) {
		if (_suspendedWords.contains(s)) {return;}
		if (_favouriteWords.contains(s)) {
			_suspendedWords.insert(s, _favouriteWords.take(s));
		} else if (_knownWords.contains(s)) {
			_suspendedWords.insert(s, _knownWords.take(s));
		} else if (_words.contains(s)) {
			_suspendedWords.insert(s, _words.take(s));
		}
	} else {
		if (!_suspendedWords.contains(s)) {return;}
		if (Preferences::forbiddenWords.Contains(s)) {
			_knownWords.insert(s, _suspendedWords.take(s));
		} else if (Preferences::favouriteWords.Contains(s)) {
			_favouriteWords.insert(s, _suspendedWords.take(s));
		} else {
			_words.insert(s, _suspendedWords.take(s));
		}
	}
}

int Dictionary::FavouriteWordsCount() const {
	return _favouriteWords.count();
}

int Dictionary::KnownWordsCount() const {
	return _knownWords.count();
}

int Dictionary::OrdinalWordsCount() const {
	return _words.count();
}

Word Dictionary::GetWord(WordType type) {
	Q_ASSERT(_loaded);
	
	const QMap<QString, WordIndex>* map = 0;

	switch (type) {
	case Ordinal:
			Q_ASSERT(_words.count() > 0);
			map = &_words;
			break;
	case Favourite:
			Q_ASSERT(_favouriteWords.count() > 0);
			map = &_favouriteWords;
			break;
	case Known:
	case Suspended:
	default:
			Q_ASSERT(false);
	}
	
	int wordNumber = qrand() % map->count();
	WordIndex ind = (*map)[map->keys()[wordNumber]];
	QString text = _loader.ReadData(ind.GetOffset(), ind.GetSize());
	
	QRegExp regexp;
	regexp.setCaseSensitivity(Qt::CaseInsensitive);
	
	int start = -1;
	int length = 0;
	QString word;
	QString transcription;
	QString translation;
	
	regexp.setPattern("(<k>)(.+)(</k>)");
	start = regexp.indexIn(text, 0);
	Q_ASSERT (start > -1);
	length = regexp.matchedLength();
	word = regexp.cap(2);
	text.remove(start, length);
	
	regexp.setPattern("(<tr>)(.+)(</tr>)");
	start = regexp.indexIn(text, 0);
	if (start > -1) {
		length = regexp.matchedLength();
		transcription = regexp.cap(2);
		text.remove(start, length);
	}
	
	translation = text;
	
	
	Word w(word, transcription, translation);
	return w;
}

QList<QString> Dictionary::GetWordsList() const {
	return _words.keys();
}
